/*
 * 
 * @Description: Used to format strings, can largely replace the library function vsprintf.
 *
 * @Version: v1.2.0
 * @Author: Hao(bds)
 * @Date: 2023/09/23
 * @Repository: https://gitee.com/bds123/f_printf
 */
#include "f_sprintf.h"

#define FORMAT_FLAG_PAD_ZERO       (1u << 1)

static const char _aV2C[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

static int __uint2str(char* buf, unsigned v, unsigned base, unsigned num_digits, unsigned field_width, unsigned format_flags) 
{
    unsigned div;
    unsigned digit;
    unsigned number;
    unsigned width;
    int offset = 0;
    char c;

    number = v;
    digit = 1u;
    
    // Get actual field width
    width = 1u;
    while (number >= base) {
        number = (number / base);
        width++;
    }
    if (num_digits > width) {
        width = num_digits;
    }
   
    // Print leading chars if necessary
    if (field_width != 0u) {
        if (((format_flags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && (num_digits == 0u)) {
            c = '0';
        } else {
            c = ' ';
        }
        while ((field_width != 0u) && (width < field_width)) {
            field_width--;
            buf[offset++] = c;
        }
    }

    // Compute Digit.
    // Loop until Digit has the value of the highest digit required.
    // Example: If the output is 345 (Base 10), loop 2 times until Digit is 100.
    while (1) {
        if (num_digits > 1u) {       // User specified a min number of digits to print? => Make sure we loop at least that often, before checking anything else (> 1 check avoids problems with NumDigits being signed / unsigned)
            num_digits--;
        } else {
            div = v / digit;
            if (div < base) {        // Is our divider big enough to extract the highest digit from value? => Done
                break;
            }
        }
        digit *= base;
    }
	
    // Output digits
    do {
        div = v / digit;
        v -= div * digit;
        buf[offset++] = _aV2C[div];
        digit /= base;
    } while (digit);
	
	buf[offset] = 0;

    return offset;
}

static int __int2str(char* buf, int v, unsigned base, unsigned num_digits, unsigned field_width, unsigned format_flags) {
    unsigned width;
    int offset = 0;
    int number;

    number = (v < 0) ? -v : v;

    // Get actual field width
    width = 1u;
    while (number >= (int)base) {
        number = (number / (int)base);
        width++;
    }
    if (num_digits > width) {
        width = num_digits;
    }
    if ((field_width > 0u) && (v < 0)) {
        field_width--;
    }

    // Print leading spaces if necessary
    if (((format_flags & FORMAT_FLAG_PAD_ZERO) == 0u) || (num_digits != 0u)) {
        if (field_width != 0u) {
            while ((field_width != 0u) && (width < field_width)) {
                field_width--;
                buf[offset++] = ' ';
            }
        }
    }

    // Print sign if necessary
    if (v < 0) {
        v = -v;
        buf[offset++] = '-';
    } else {

    }

    int x_offset = __uint2str(buf + offset, (unsigned)v, base, num_digits, field_width, format_flags);
    offset += x_offset;
	
	buf[offset] = 0;

    return offset;
}

static int __llu2str(char* buf, unsigned long long v)
{
    int offset = 0;

    if (v == 0) {
        buf[offset++] = '0';
    } else {
        char str[21];  // 20 digits for max v value
        int n = 0;
        unsigned long long temp = v;
        char c;
        while (temp != 0) {
            c = _aV2C[temp % 10];
            temp /= 10;
            str[n++] = c;
        }
        while (n > 0) {
            buf[offset++] = str[--n];
        }
    }
	
	buf[offset] = 0;

    return offset;
}

static int __float2str(char* buf, float v, unsigned num_digits, unsigned field_width)
{
    int offset = 0;
    int v_int = v;
    float fv = v;

    if (v_int != 0) {
        offset += __int2str(buf + offset, v_int, 10u, 0, field_width, FORMAT_FLAG_PAD_ZERO);
    } else {
        if (fv < 0) {
            buf[offset++] = '-';
            buf[offset++] = '0';
        } else {
            buf[offset++] = '0';
        }
    }
    buf[offset++] = '.';
    unsigned n = num_digits;

    if (n > 6) {
        n = 6;
    }
    if (n == 0) {
        n = num_digits = 6;
    }

    fv *= fv < 0 ? -1 : 1;
    v_int *= v_int < 0 ? -1 : 1;
    fv = fv - v_int;
    while (n--) {
        fv *= 10;
    }

    v_int = fv;
    offset += __int2str(buf + offset, v_int, 10u, num_digits, 0, FORMAT_FLAG_PAD_ZERO);

    buf[offset] = 0;

    return offset;
}

int int2str(char *buf, int v)
{
	return __int2str(buf, v, 10, 0, 0, FORMAT_FLAG_PAD_ZERO);
}

int int2str_ex(char *buf, int v, int field_width)
{
	return __int2str(buf, v, 10, 0, field_width, FORMAT_FLAG_PAD_ZERO);
}

int uint2str(char *buf, unsigned v)
{
	return __uint2str(buf, v, 10, 0, 0, FORMAT_FLAG_PAD_ZERO);
}

int uint2str_ex(char *buf, unsigned v, int field_width)
{
	return __uint2str(buf, v, 10, 0, field_width, FORMAT_FLAG_PAD_ZERO);
}

int float2str(char *buf, float v, int precision)
{
	return __float2str(buf, v, precision, 0);
}

int llu2str(char *buf, unsigned long long v)
{
	return __llu2str(buf, v);
}

int lld2str(char* buf, long long v)
{
    int offset = 0;

    if (v < 0) {
        buf[offset++] = '-';
        v = -v;
    }
    offset += __llu2str(buf + offset, v);

    return offset;
}

int f_vsprintf(char* buf, const char* s_format, va_list* param_list) 
{
    char c;
	unsigned char ll_count = 0;
    int v;
    unsigned num_digits;
    unsigned format_flags;
    unsigned field_width;
    unsigned offset = 0;

    do {
        c = *s_format;
        s_format++;
        if (c == 0u) {
            break;
        }
        if (c == '%') {
            // Filter out flags
            format_flags = 0u;
            v = 1;
            do {
                c = *s_format;
                switch (c) {
                    case '0': format_flags |= FORMAT_FLAG_PAD_ZERO; s_format++; break;
                    default:  v = 0; break;
                }
            } while (v);
            // filter out field with
            field_width = 0u;
            do {
                c = *s_format;
                if ((c < '0') || (c > '9')) {
                    break;
                }
                s_format++;
                field_width = (field_width * 10u) + ((unsigned)c - '0');
            } while (1);

            // Filter out precision (number of digits to display)
            num_digits = 0u;
            c = *s_format;
            if (c == '.') {
                s_format++;
                do {
                    c = *s_format;
                    if ((c < '0') || (c > '9')) {
                        break;
                    }
                    s_format++;
                    num_digits = num_digits * 10u + ((unsigned)c - '0');
                } while (1);
            }
            // Filter out length modifier
            c = *s_format;
            do {
                if ((c == 'l') || (c == 'h')) {
					if (c == 'l') {
                       ll_count++;
                    }
                    s_format++;
                    c = *s_format;
                }
                else {
                    break;
                }
            } while (1);
            // Handle specifiers
            switch (c) {
            case 'c': {
                char c0;
                v = va_arg(*param_list, int);
                c0 = (char)v;
                buf[offset++] = c0;
                break;
            }
            case 'd':
                if (ll_count != 2) {
                    v = va_arg(*param_list, int);
                    offset += __int2str(buf + offset, v, 10u, num_digits, field_width, format_flags);
                } else {
					offset += lld2str(buf + offset, va_arg(*param_list, long long));
                }
				ll_count = 0;
                break;
            case 'u':
                if (ll_count != 2) {
                    v = va_arg(*param_list, int);
                    offset += __uint2str(buf + offset, (unsigned)v, 10u, num_digits, field_width, format_flags);
                } else {
                    offset += __llu2str(buf+offset, va_arg(*param_list, unsigned long long));
                }
				ll_count = 0;
                break;
            case 'x':
            case 'X':
                v = va_arg(*param_list, int);
                offset += __uint2str(buf + offset, (unsigned)v, 16u, num_digits, field_width, format_flags);
                break;
            case 's': {
                const char* s = va_arg(*param_list, const char*);
                do {
                    c = *s;
                    s++;
                    if (c == '\0') {
                        break;
                    }
                    buf[offset++] = c;
                } while (1);
            }
            break;
            case 'p':
                v = va_arg(*param_list, int);
                offset += __uint2str(buf + offset, (unsigned)v, 16u, 8u, 8u, 0u);
                break;
            case '%':
                buf[offset++] = '%';
                break;
            case 'f': 
				offset += __float2str(buf + offset, (float)va_arg(*param_list, double), num_digits, field_width);
                break;
            default:
                break;
            }
            s_format++;
        } else {
            buf[offset++] = c;
        }
    } while (1);

    buf[offset] = 0;

    return offset;
}

int f_sprintf(char* buf, const char* s_format, ...)
{
    int offset;

    va_list param_list;
    va_start(param_list, s_format);
    offset = f_vsprintf(buf, s_format, &param_list);
    va_end(param_list);

    return offset;
}

